/* Copyright 2009 Requirement Management System
 * 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.mags.dao.hibernate;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.hibernate.type.Type;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Subqueries;
import org.mags.dao.common.BooleanOperator;
import org.mags.dao.common.Interval;
import org.mags.dao.common.RestrictionAttribute;
import org.mags.dao.common.RestrictionOperator;
import org.mags.dao.common.RestrictionBuilder;
import org.mags.dao.common.Attribute;

/**
 * @author Mario Gait�n
 */

public class HibernateRestrictionBuilder implements RestrictionBuilder, Serializable {	
	private static final long serialVersionUID = 1L;
	private List<Object> list = new ArrayList<Object>();
	private static final String[] operators = new String[]{"BETWEEN", "=", ">=", ">", "IN", "NIN", "<=", "LIKE", "<", "<>", "EXISTS", "NOTEXISTS"};
	private static final RestrictionOperator[] enumOperators 
	= new RestrictionOperator[]{RestrictionOperator.BT
							   , RestrictionOperator.EQ
							   , RestrictionOperator.GE
							   , RestrictionOperator.GT
							   , RestrictionOperator.IN
							   , RestrictionOperator.NIN
							   , RestrictionOperator.LE
							   , RestrictionOperator.LKIC
							   , RestrictionOperator.LT
							   , RestrictionOperator.NE
							   , RestrictionOperator.EXISTS
							   , RestrictionOperator.NOTEXISTS};
	@Override
	public String toString() {
		if(list==null)
			return super.toString();
		else
			return list.toString();
	}
	@Override
	public Object condition(RestrictionAttribute restriction) {
		return condition((String)restriction.getName(), restriction.getOperator(), restriction.getValue());
	}
	private Object condition(String propertyName, RestrictionOperator operator, Object value) {
		if (operator==RestrictionOperator.EQ) 
			return conditionEq(propertyName, value, false);
		else if(operator==RestrictionOperator.EQIC)
			return conditionEq(propertyName, value, true);
		else if (operator==RestrictionOperator.NE) 
			return conditionNe(propertyName, value);
		else if (operator==RestrictionOperator.GE) 
			return conditionGe(propertyName, value);
		else if (operator==RestrictionOperator.GT) 
			return conditionGt(propertyName, value);
		else if (operator==RestrictionOperator.LE) 
			return conditionLe(propertyName, value);
		else if (operator==RestrictionOperator.LT) 
			return conditionLt(propertyName, value);
		else if (operator==RestrictionOperator.LKIC) 
			return conditionLk(propertyName, value, true);
		else if (operator==RestrictionOperator.LK) 
			return conditionLk(propertyName, value, false);
		else if (operator==RestrictionOperator.IN) 
			return conditionIn(propertyName, value);
		else if (operator==RestrictionOperator.NIN) 
			return conditionNotIn(propertyName, value);
		else if (operator==RestrictionOperator.EXISTS) 
			return conditionExists(value);
		else if(operator==RestrictionOperator.NOTEXISTS) 
			return conditionNotExists(value);
		else if(operator==RestrictionOperator.BT) {
			Interval interval = (Interval)value;
			return conditionBt(propertyName, interval.getLow(), interval.getHigh());
		}
		else 
			throw new RuntimeException("operator "+operator.name()+" is not exists");
	}
	@Override
	public RestrictionOperator toEnumOperator(String operator) {
		if(operator==null||"".equals(operator))
			throw new RuntimeException("operator is not correct");
		String _operator = operator.toUpperCase();
		int index = 0;
		for(String op: operators) {
			if(op.equals(_operator))
				return enumOperators[index];
			index++;
		}
		return null;
	}	
	@Override
	public boolean isEmpty() {
		return list.size()==0;
	}
	@Override
	public Collection<?> getRestriction() {
		List<Object> l = list;
		list = new ArrayList<Object>();
		return l;
	}
	@Override
	public void addConditionSql(String sql, Attribute[] attributes, BooleanOperator op) {
		Object condition;
		if(attributes!=null) {
			Type[] _types = new Type[attributes.length];
			Object[] _values = new Object[attributes.length];
			int i = 0;
			for(Attribute attribute: attributes) {
				_types[i] = (Type)attribute.getType();
				_values[i] = attribute.getValue();
			}
			condition = Restrictions.sqlRestriction(sql, _values, _types);
		}	
		else
			condition = Restrictions.sqlRestriction(sql);	
		addInList(condition, op);
	}
	@Override
	public void addCondition(Object condition) {
		addCondition(condition, BooleanOperator.AND);
	}
	@Override
	public void addCondition(Object condition, BooleanOperator op) {
		addInList(condition, op);
	}
	private void addInList(Object condition, BooleanOperator op) {
		if(op==null)op = BooleanOperator.AND;
		Object _condition = null;
		if(condition instanceof RestrictionBuilder)
			_condition = ((RestrictionBuilder)condition).getRestriction();
		else if(condition instanceof RestrictionAttribute) {
			RestrictionAttribute resAtt = (RestrictionAttribute)condition;
			_condition = condition(resAtt.getName(), resAtt.getOperator(), resAtt.getValue());
		} else
			_condition = condition;
		List<Object> _list = list;
		if(op==BooleanOperator.OR && list.size() > 0) {
			int index = list.size()-1;
			Object lastElement = list.get(index);
			if(lastElement instanceof List) {
				_list = (List<Object>)lastElement;
			} else {
				_list = new ArrayList<Object>();
				_list.add(lastElement);
				list.set(index, _list);
			}
		}
		if(op==BooleanOperator.AND && _condition instanceof Collection) {
			Collection<Object> collection = (Collection<Object>)_condition;
			for(Object o: collection) 
				_list.add(o);
		} else
			_list.add(_condition);
	}	
	public Object condition(Object condition1, BooleanOperator op, Object condition2) {
		if(op==BooleanOperator.AND)
			return Restrictions.and((Criterion)condition1, (Criterion)condition2);
		else if(op==BooleanOperator.OR)
			return Restrictions.or((Criterion)condition1, (Criterion)condition2);
		else
			throw new RuntimeException("operator is not supported");
	}
	private Object conditionBt(String propertyName, Object lo, Object hi) {
		return Restrictions.between(propertyName, lo, hi);		
	}
	private Object conditionLk(String propertyName, Object value, boolean ignoreCase) {
		if(ignoreCase)
			return Restrictions.like(propertyName, value).ignoreCase();
		else
			return Restrictions.like(propertyName, value);
	}
	private Object conditionExists(Object value) {
		return Subqueries.exists((DetachedCriteria)value);
	}
	private Object conditionNotExists(Object value) {
		return Subqueries.notExists((DetachedCriteria)value);
	}
	private Object conditionEq(String propertyName, Object value, boolean ignoreCase) {		
		if(value!=null) {					
			if(value instanceof DetachedCriteria)
				return Subqueries.propertyEq(propertyName, (DetachedCriteria)value);
			else {
				if(ignoreCase)
					return Restrictions.eq(propertyName, value).ignoreCase();
				else
					return Restrictions.eq(propertyName, value);
			}
		}
		else
			return Restrictions.isNull(propertyName);		
	}
	private Object conditionNe(String propertyName, Object value) {
		if(value!=null) {
			if(value instanceof DetachedCriteria)
				return Subqueries.propertyNe(propertyName, (DetachedCriteria)value);
			else			
				return Restrictions.ne(propertyName, value);
		}
		else
			return Restrictions.isNotNull(propertyName);
	}
	private Object conditionGe(String propertyName, Object value) {
		if(value instanceof DetachedCriteria)
			return Subqueries.propertyGe(propertyName, (DetachedCriteria)value);
		else		
			return Restrictions.ge(propertyName, value);
	}
	private Object conditionGt(String propertyName, Object value) {
		if(value instanceof DetachedCriteria)
			return Subqueries.propertyGt(propertyName, (DetachedCriteria)value);
		else		
			return Restrictions.gt(propertyName, value);
	}
	private Object conditionLe(String propertyName, Object value) {
		if(value instanceof DetachedCriteria)
			return Subqueries.propertyLe(propertyName, (DetachedCriteria)value);
		else		
			return Restrictions.le(propertyName, value);
	}
	private Object conditionLt(String propertyName, Object value) {
		if(value instanceof DetachedCriteria)
			return Subqueries.propertyLt(propertyName, (DetachedCriteria)value);
		else
			return Restrictions.lt(propertyName, value);
	}
	private Object conditionNotIn(String propertyName, Object value) {
		Object in = conditionIn(propertyName, value);
		return Restrictions.not((Criterion)in);
	}
	private Object conditionIn(String propertyName, Object value) {
		if(value instanceof Collection)
			return Restrictions.in(propertyName, ((Collection)value));
		else if(value instanceof DetachedCriteria)
			return Subqueries.propertyIn(propertyName, (DetachedCriteria)value);
		else if(value instanceof Object[]) 
			return Restrictions.in(propertyName, (Object[])value);
		return null;
	}
}
